﻿using System.Collections.Generic;
using System.Linq;
using System;
using System.Linq.Expressions;

/// Modified By: Charles Cook
/// 
/// Provides IQueryable Paging.  The advantage to using
/// an IQueryable data source is that SQL Server Paging will be 
/// performed (when SQL is the target). Note that paging upon a
/// List was supported and can easily be added, but it has been removed
/// to prevent in memory paging.  
/// 
/// Based upon the PagedList<T> first used by Scott Guthrie.  Extended
/// to support filtering and sorting upon the source by using the reflective
/// Sort and Filter methods from the ExpressionHelper.
/// 
/// For debugging queries use: http://weblogs.asp.net/scottgu/archive/2007/07/31/linq-to-sql-debug-visualizer.aspx

namespace App.Utility.Paging
{
    public class PagedList<T> : List<T>, IPagedList
    {
        /// <summary>
        /// Request a page from a queryable source
        /// </summary>
        /// <param name="source">The source to query</param>
        /// <param name="index">The page to retrieve (zero based)</param>
        /// <param name="pageSize">The size of the pages</param>
        public PagedList(IQueryable<T> source, int index, int pageSize)
        {
            // Count pages
            CalculatePaging(source, index, pageSize);

            // Build the expression
            var q = source.Skip(index * pageSize).Take(pageSize);

            // Evaluate the query
            this.AddRange(q.ToList());
        }

        /// <summary>
        /// Request a page from a queryable source with optional filtering
        /// and sorting defined within <paramref name="PageRequest"/>
        /// </summary>
        /// <param name="source">The source to query</param>
        /// <param name="PageRequest">The PageRequest information</param>
        public PagedList(IQueryable<T> source, PageRequest PageRequest)
        {
            IQueryable<T> q = source;

            // Apply filters
            q = FilterSource(source, PageRequest);

            // Count pages on the filtered query
            CalculatePaging(q, PageRequest.Index, PageRequest.PageSize);

            // Perform nested sorts
            foreach (Sort sort in PageRequest.Sorts)
                q = ExpressionHelper<T>.Sort(
                    q, sort.Parameter, sort.TypeName, sort.Ascending);

            // Get the page
            q = q.Skip(PageRequest.Index * PageRequest.PageSize).Take(PageRequest.PageSize);
            this.AddRange(q.ToList());
        }


        /// <summary>
        /// Request a page from a queryable source with optional filtering
        /// and sorting defined within <paramref name="PageRequest"/>
        /// </summary>
        /// <param name="source">The source to query</param>
        /// <param name="PageRequest">The PageRequest information</param>
        /// <param name="TotalPages">The TotalNumber of pages</param>
        public PagedList(IQueryable<T> source, PageRequest PageRequest, out int TotalPages)
        {
            IQueryable<T> q = source;

            // Apply filters
            q = FilterSource(source, PageRequest);

            // Count pages on the filtered query
            CalculatePaging(q, PageRequest.Index, PageRequest.PageSize);

            TotalPages = this.TotalPages;

            // Perform nested sorts
            foreach (Sort sort in PageRequest.Sorts)
                q = ExpressionHelper<T>.Sort(
                    q, sort.Parameter, sort.TypeName, sort.Ascending);

            // Get the page
            q = q.Skip(PageRequest.Index * PageRequest.PageSize).Take(PageRequest.PageSize);
            this.AddRange(q.ToList());
        }


        private void CalculatePaging(IQueryable<T> source, int index, int pageSize)
        {
            // Note, this hits the repository
            int total = source.Count();
            this.TotalCount = total;
            this.TotalPages = total / pageSize;

            if (total % pageSize > 0)
                TotalPages++;

            this.PageSize = pageSize;
            this.PageIndex = index;
        }

        /// <summary>
        /// Create a page response of the page
        /// </summary>
        /// <returns>A page response for the paged list</returns>
        public PageResponse<T> GetPageResponse()
        {
            PageResponse<T> resp = new PageResponse<T>();

            resp.Page = this.ToList();
            resp.TotalPages = TotalPages;
            resp.PageIndex = PageIndex;
            resp.TotalCount = TotalCount;
            resp.PageSize = PageSize;

            return resp;
        }


        public int TotalPages { get; set; }
        public int TotalCount { get; set; }
        public int PageIndex { get; set; }
        public int PageSize { get; set; }

        public bool IsPreviousPage
        {
            get
            {
                return (PageIndex > 0);
            }
        }

        public bool IsNextPage
        {
            get
            {
                return (PageIndex * PageSize) <= TotalCount;
            }
        }



        private static IQueryable<T> FilterSource(IQueryable<T> source, PageRequest PageRequest)
        {
            IQueryable<T> q = source;

            if (PageRequest.Filters.Count > 0)
            {
                var param = Expression.Parameter(typeof(T), "item");

                if (PageRequest.FilterType == PageRequest.FilterTypes.And)
                {

                    // Append filters
                    foreach (Filter filter in PageRequest.Filters)
                        q = ExpressionHelper<T>.Filter(
                            q, filter.Parameter, filter.TypeName, filter.Value, filter.BinaryExpression, param);
                }
                else
                {
                    // for mixed types convert to string
                    Expression<Func<T, bool>> constraint = ExpressionHelper<T>.FilterExpressionAsString(PageRequest.Filters[0].Parameter, PageRequest.Filters[0].TypeName, PageRequest.Filters[0].Value, PageRequest.Filters[0].BinaryExpression, param); ;
                    for (int i = 1; i < PageRequest.Filters.Count; i++)
                        constraint = ExpressionHelper<T>.OrExpressions(constraint,
                            ExpressionHelper<T>.FilterExpressionAsString(PageRequest.Filters[i].Parameter, PageRequest.Filters[i].TypeName, PageRequest.Filters[i].Value, PageRequest.Filters[i].BinaryExpression, param),
                            param);
                    return ExpressionHelper<T>.Filter(q,constraint);
                    
                 }
            }

            return q;
        }
    }

    /// <summary>
    /// Extension methods to provide paging on IQueryable
    /// </summary>
    public static class Pagination
    {
        /// <summary>
        /// Request a page from a queryable source
        /// </summary>
        /// <typeparam name="T">The object being queried</typeparam>
        /// <param name="source">The source to query</param>
        /// <param name="index">Page to retrieve (zero based)</param>
        /// <param name="pageSize">The size of the pages to use</param>
        /// <returns>PagedList with the page in memory</returns>
        public static PagedList<T> ToPagedList<T>(this IQueryable<T> source, int index, int pageSize)
        {
            return new PagedList<T>(source, index, pageSize);
        }

        /// <summary>
        /// Request a page from a queryable source using the default page size
        /// </summary>
        /// <typeparam name="T">The object being queried</typeparam>
        /// <param name="source">The source to query</param>
        /// <param name="index">Page to retrieve (zero based)</param>
        /// <returns>PagedList with the page in memory</returns>
        public static PagedList<T> ToPagedList<T>(this IQueryable<T> source, int index)
        {
            return new PagedList<T>(source, new PageRequest(index));
        }

        /// <summary>
        /// Request a page from a queryable source with optional filtering
        /// and sorting defined within <paramref name="PageRequest"/>
        /// </summary>
        /// <typeparam name="T">The object being queried</typeparam>
        /// <param name="source">The source to query</param>
        /// <param name="PageRequest">The PageRequest information</param>
        /// <returns></returns>
        public static PagedList<T> ToPagedList<T>(this IQueryable<T> source, PageRequest PageRequest)
        {
            return new PagedList<T>(source, PageRequest);
        }

        /// <summary>
        /// A terse method for requesting a page of a queryable source with optional
        /// filtering and sorting defined within <paramref name="PageRequest"/>
        /// </summary>
        /// <typeparam name="T">The object being queried</typeparam>
        /// <param name="source">The source to query</param>
        /// <param name="PageRequest">The PageRequest information</param>
        /// <param name="TotalPages">The total number of pages in the source</param>
        /// <returns>List of type <typeparamref name="T"/> containing the requested page</returns>
        public static List<T> ToPagedList<T>(this IQueryable<T> source, PageRequest PageRequest, out int TotalPages)
        {
            return new PagedList<T>(source, PageRequest, out TotalPages).ToList();
        }

        /// <summary>
        /// A terse method for requesting a PageResponse of a queryable source with
        /// optional filtering and sorting defined within <paramref name="PageRequest"/>
        /// </summary>
        /// <typeparam name="T">The object being queried</typeparam>
        /// <param name="source">The source to query</param>
        /// <param name="PageRequest">The PageRequest information</param>
        /// <returns>The PageResponse containg the paged data and the page count</returns>
        public static PageResponse<T> ToPageResponse<T>(this IQueryable<T> source, PageRequest PageRequest)
        {
            return new PagedList<T>(source, PageRequest).GetPageResponse();
        }
    }


}
